/************************************************************************************************
 * Name          : verificationSearch
 * Description   : A Verification Address Search plugin used to search address based on
 *                 specific country.
 * Example       : $("#searchContainer").verificationSearch({
 *                       proxyPath: "addressValidation.ashx",
 *                       timeout: 5000,
 *                       inputAddress: "inputAddress",
 *                       inputCountry: "inputCountry",
 *                       onSuccess: function (data) {},
 *                       onError: function (data, status) {},
 *                       styles: {
 *                           resultContainer: "resultContainer",
 *                           picklist: "picklist",
 *                           refineContainer: "refineContainer",
 *                           statusIndicator: "statusIndicator",
 *                           picklistItem: "picklistItem",
 *                           error: "error",
 *                           loading: "loading",
 *                           refineError: "refineError",
 *                           refineButton: "refineButton",
 *                           refineHeader: "refineHeader",
 *                           refineText: "refineText",
 *                           refineBox: "refineBox"
 *                       }
 *                   });
 ************************************************************************************************/

(function ($, _window, undefined) {
    var pluginName = "verificationSearch",
        xhr = null,
        elementIds = {
            searchBox: $('#objBody_content_0_pagecontent_0_txtAddress').length > 0 ? "#objBody_content_0_pagecontent_0_txtAddress" : "#objBody_content_0_leftcolumn_0_txtAddress",
            resultContainerId: "resultContainer",
            picklistId: "picklist",
            statusIndicatorId: "statusIndicator",
            refineContainerId: "refineContainer",
            refineErrorId: "refineError",
            refineButtonId: "refineButton",
            refineHeaderId: "refineHeader",
            refineTextId: "refineText",
            refineBoxId: "refineBox",
            loadingId: "loading",
            errorIndicatorId: "errorIndicator"
        },
        styles = {
            resultContainer: "resultContainer",
            picklist: "picklist",
            picklistItem: "picklistItem",
            picklistItemText: "picklistItemText",
            refineContainer: "refineContainer",
            refineHeader: "refineHeader",
            refineText: "refineText",
            refineLabel: "form-label",
            refineBox: "form-control mb-4",
            refineButton: "btn btn-primary",
            refineError: "refineError",
            errorIndicator: "errorImg",
            loadingIndicator: "loading"
        },
        messages = {
            interactionRequired: {
                header: addressVerifyInteractionHeader,
                text: addressVerifyInteractionText,
                button: addressVerifyInteractionButton
            },
            premisePartial: {
                header: addressVerifyPremisesHeader,
                text: addressVerifyPremisesText,
                button: addressVerifyPremisesButton,
                error: addressVerifyPremisesError
            },
            streetPartial: {
                header: addressVerifyStreetHeader,
                text: addressVerifyStreetText,
                button: addressVerifyStreetButton,
                error: addressVerifyStreetError
            },
            multiple: {
                header: addressVerifyMultipleHeader,
                text: addressVerifyMultipleText
            },
            verify: {
                notVerified: addressVerifyCannotBeVerified,
                caAddress: addressVerifyCAAddress,
                addressRequired: addressVerifyAddressRequired,
                addressVerifyEmptyUnitNumber: addressVerifyEmptyUnitNumber
            },
            error: {
                error: addressVerifyError,
                timeout: addressVerifyTimeout
            }
        },
        ajaxErrorMessages = {
            invalidData: addressVerifyBadData,
            resultMissingId: addressVerifyMissingID,
            serverNotFound: addressVerifyCannotBeVerified,
            addressVerificationDown: addressDownMsg
        },
        // public enum VerificationLevel { Verfied = 1, InteractionRequired, PremisesPartial, StreetPartial, Multiple, Unknown, None }
        verificationLevel = {
            Verified: 1,
            InteractionRequired: 2,
            PremisesPartial: 3,
            StreetPartial: 4,
            Multiple: 5,
            Unknown: 6,
            None: 7,
            NoRefine: 8,
            UnableToConnectToRemoteServer: 800,
            InternalServerError: 801,
            OperationHasTimedOut: 802,
            UnderlyingConnectionClosed: 803,
            RemoteNameCouldNotBeResolved: 804,
            BadGateway: 805,
            ServiceUnavailable: 806,
            BadRequest: 807,
            NotFound: 808
        };

    // verification plugin.
    var verificationPlugin = function (element, options) {
        this.element = element;
        this.options = $.extend(true, {}, $.fn[pluginName].defaults, options);
    };

    $.fn[pluginName] = function (options) {
        if ($(this).is("div") == false) {
            throw "Target element must be of type <div>";
        }

        if (!$.data(this, "plugin_" + pluginName)) {
            $.data(this, "plugin_" + pluginName, new verificationPlugin(this, options));
        }

        var $plugin = $.data(this, "plugin_" + pluginName);
        $plugin.render();

        return $plugin;
    };

    $.fn[pluginName].defaults = {
        proxyPath: "addressValidation.ashx",
        timeout: 120000,
        inputAddress: "inputAddress",
        inputCountry: "inputCountry",
        styles: styles,
        messages: messages,
        onSuccess: null,
        onError: null
    };

    // Address plugin prototype methods.
    verificationPlugin.prototype = {
        //getAddresses: function () {
        //    return getAddresses.call(this);
        //},
        render: function () {
            bindEvents.call(this);
        },
        getCountry: function () {
            return getCountry.call(this);
        },
        renderPicklist: function (items, verifyLevel) {
            renderPicklistElements.call(this, items, verifyLevel);
            bindPicklistElementEvents.call(this, items);
        },
        renderRefineBox: function (data, verifyLevel) {
            renderRefineElements.call(this, data, verifyLevel);
        },
        renderError: function (jqXHR, status, errorThrown, verifyLevel) {
            renderErrorElements.call(this, jqXHR, status, errorThrown, verifyLevel);
        },
        verify: function () {
            reset();
            resetStatusElement();

            // Render result container.
            renderResultContainerElements.call(this);
            renderLoadingElements.call(this, this.resultContainer.children("div"), false, "Loading...");

            // Get the address to verify.
            var $searchBox = $('#objBody_content_0_pagecontent_0_txtAddress').length > 0 ? $("#objBody_content_0_pagecontent_0_txtAddress") : $("#objBody_content_0_leftcolumn_0_txtAddress");
            var address = $searchBox.val();
            var country = "USA";
            this.options.country = country;

            // Error on empty address
            if (address == null || address == undefined || address == '') {
                onError.call(this, undefined, "emptyAddress", this.options.messages.verify.addressRequired);
                clearLoading();
                return;
            }

            // Check for CA Address
            //if (!address.includes(" CA") && !address.includes(" ca")) {
            //    onError.call(this, undefined, "notCA", this.options.messages.verify.caAddress);
            //    clearLoading();
            //    return;
            //}
            //if (address.indexOf(" CA") === -1 && address.indexOf(" ca") === -1 && address.indexOf(",CA") === -1 && address.indexOf(",ca") === -1) {
            //    onError.call(this, undefined, "notCA", this.options.messages.verify.caAddress);
            //    clearLoading();
            //    return;
            //}

            if (address.toLowerCase().indexOf("apt") < 0 && address.indexOf("#") > 0) {
                address = address.replace("#", "apt");
            }

            // Strip special characters from address
            address = address.replace(/[^\w\s,-\/]/gi, '');

            // Trim whitespace from the address
            address = address.trim();

            $searchBox.val(address);

            // Replace forward slashes in address for transmission
            address = address.replace(/[\/]/gi, '~');

            verifyAddressCall.call(this, address);
            //ajaxCall.call(this, "verify", address, undefined, onVerifySuccess, onError);
        },
        refine: function (query, moniker) {

            // Strip special characters from unit number
            query = query.replace(/[^\w\s\.,#-\/]/gi, '');

            // Replace forward slashes in address for transmission
            query = query.replace(/[\/]/gi, '~');

            refineAddressCall.call(this, query, moniker);
            //ajaxCall.call(this, "refine", query, moniker, onRefineSuccess, onError);
        },
        final: function (moniker) {
            ajaxCall.call(this, "final", undefined, moniker, onFinalSuccess, onError);
        },
        reset: function () {
            reset();
        }
    };

    /************************
     **** PRIVATE METHODS ****
     *************************/

    // Bind events to singleline search elements.
    function bindEvents() {
        var $this = this;
        //var $searchButton = $("#" + elementIds.searchButton + this.uniqueId);
        var $searchBox = $('#objBody_content_0_pagecontent_0_txtAddress').length > 0 ? $("#objBody_content_0_pagecontent_0_txtAddress") : $("#objBody_content_0_leftcolumn_0_txtAddress");
        var $loadingIndicator = $("#" + elementIds.loadingIndicator + this.uniqueId);

        $searchBox.change(function (event) {
            if ($loadingIndicator.length === 0) {
                $this.verify.call($this);
            }
        });

        // Clear validation on form reset.
        $("#my_profile").on('reset', function (event) {
            reset();
            resetStatusElement();
        });
    }

    // Make our Verify Address call
    function verifyAddressCall(query) {
        var $this = this;

        // Abort previous ajax call.
        if (xhr) {
            xhr.abort();
        }

        console.log(addressValidationUri);

        xhr = $.ajax({
            url: addressValidationUri + "/" + encodeURIComponent(query),
            type: 'GET',
            data: {},
            timeout: validationTimeout,
            success: function (data, status, jqXHR) {
                onVerifySuccess.call($this, data);
            },
            error: function (req, status, errThrown) {
                onError.call($this, req, status, errThrown);
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("Authorization", validationAuthCode);
            },
        });
    }

    // Make our refine address call
    function refineAddressCall(query, id) {
        var $this = this;

        // Abort previous ajax call.
        if (xhr) {
            xhr.abort();
        }

        console.log(addressValidationRefineUri);

        xhr = $.ajax({
            url: addressValidationRefineUri + "?id=" + encodeURI(id) + "&query=" + encodeURI(query),
            type: 'GET',
            data: {},
            timeout: validationTimeout,
            success: function (data, status, jqXHR) {
                onRefineSuccess.call($this, data);
            },
            error: function (req, status, errThrown) {
                onError.call($this, req, status, errThrown);
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("Authorization", validationAuthCode);
            },
        });
    }

    function finalCall(id) {
        var $this = this;

        // Abort previous ajax call.
        if (xhr) {
            xhr.abort();
        }

        console.log(addressValidationRefineUri);

        xhr = $.ajax({
            url: addressValidationRefineUri + "?id=" + encodeURI(id) + "&query" + encodeURI(query),
            type: 'GET',
            data: {},
            timeout: validationTimeout,
            success: function (data, status, jqXHR) {
                onRefineSuccess.call($this, data);
            },
            error: function (req, status, errThrown) {
                onError.call($this, req, status, errThrown);
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("Authorization", validationAuthCode);
            },
        });
    }


    // Ajax call to proxy.
    function ajaxCall(action, query, moniker, onSuccess, onError) {
        var $this = this;

        // Abort the previous call if there is any.
        if (xhr) {
            xhr.abort();
        }

        xhr = $.ajax({
            url: $this.options.proxyPath,
            timeout: $this.options.timeout,
            type: 'GET',
            data: {
                action: action,
                query: query,
                id: moniker,
                country: $this.options.country
            },
            dataType: "json",
            success: function (data, status, jqXHR) {
                onSuccess.call($this, data, status, jqXHR);
            },
            error: function (jqXHR, status, errThrown) {
                onError.call($this, jqXHR, status, errThrown);
            }
        });
    }


    // Verify success handler.
    function onVerifySuccess(data, status) {
        clearLoading();

        if (!data || !data.verificationLevel) {
            onError.call(this, data, "error", ajaxErrorMessages.invalidData);
            return;
        }

        var verifyLevel = data.verificationLevel;

        // Render workflow based on verify level.
        switch (verifyLevel) {
            case verificationLevel.Verified:
                onFinalSuccess.call(this, data, status);
                break;

            case verificationLevel.InteractionRequired:
                var $searchBox = $('#objBody_content_0_pagecontent_0_txtAddress').length > 0 ?
                    $("#objBody_content_0_pagecontent_0_txtAddress") :
                    $("#objBody_content_0_leftcolumn_0_txtAddress");
                var address = $searchBox.val();
                if (address === data.selectedAddress) {
                    onFinalSuccess.call(this, data, status);
                } else {
                    this.renderRefineBox(data, verifyLevel);
                }

                break;

            case verificationLevel.PremisesPartial:
            case verificationLevel.StreetPartial:
            case verificationLevel.Multiple:
                if (!data.suggestedAddresses || !$.isArray(data.suggestedAddresses) || data.suggestedAddresses.length < 1) {
                    onError.call(this, data, "error", ajaxErrorMessages.invalidData);
                    return;
                }

                // Check if there is a refine action in the results.
                var hasRefine = false;
                $.each(data.suggestedAddresses, function (index, value) {
                    if (value.action && value.action.toLowerCase() === "refine") {
                        hasRefine = true;
                        return false;
                    }
                });

                if (!hasRefine) {
                    verifyLevel = verificationLevel.NoRefine;
                }

                $("#" + elementIds.resultContainerId).empty();

                this.renderRefineBox(data.suggestedAddresses, verifyLevel);
                this.renderPicklist(data.suggestedAddresses, verifyLevel);
                break;
            case verificationLevel.Unknown:
            case verificationLevel.None:
                onError.call(this, data, "notFound", this.options.messages.verify.notVerified);
                break;
            case verificationLevel.UnableToConnectToRemoteServer:
            case verificationLevel.InternalServerError:
            case verificationLevel.OperationHasTimedOut:
            case verificationLevel.UnderlyingConnectionClosed:
            case verificationLevel.RemoteNameCouldNotBeResolved:
            case verificationLevel.BadGateway:
            case verificationLevel.ServiceUnavailable:
            case verificationLevel.BadRequest:
            case verificationLevel.NotFound:
                onError.call(this, data, "down", ajaxErrorMessages.addressVerificationDown);
                break;
            default:
                onError.call(this, data, "serverNotFound", ajaxErrorMessages.serverNotFound);
                break;
        }
    }

    // Refine success handler.
    function onRefineSuccess(data, status, jqXHR) {
        clearLoading();

        // Error out if addres validation is down
        if (data.verificationLevel == verificationLevel.UnableToConnectToRemoteServer ||
            data.verificationLevel == verificationLevel.InternalServerError ||
            data.verificationLevel == verificationLevel.OperationHasTimedOut ||
            data.verificationLevel == verificationLevel.UnderlyingConnectionClosed ||
            data.verificationLevel == verificationLevel.RemoteNameCouldNotBeResolved ||
            data.verificationLevel == verificationLevel.BadGateway) {
            onError.call(this, data, "down", ajaxErrorMessages.addressVerificationDown);
            return;
        }

        if (!data || !data.suggestedAddresses || !$.isArray(data.suggestedAddresses)) {
            onError.call(this, data, "error", ajaxErrorMessages.invalidData);
            return;
        }

        // On Refine success, we should have one suggestion
        if (data.suggestedAddresses.length == 1) {
            if (!data.suggestedAddresses[0].id) {
                onError.call(this, data, "down", ajaxErrorMessages.resultMissingId);
                return;
            }

            // Validate our updated address
            verifyAddressCall.call(this, data.suggestedAddresses[0].text);
        }
        // PWS-757: This is a hack to resolve an error refining apartment number.
        // When the user selects the first apartment number in a range, we're getting back verify none,
        // and the corrected address is in the first index of the suggested addresses. So in this case,
        // we'll take the first suggested address.
        else if (data.verificationLevel == verificationLevel.None && data.suggestedAddresses.length == 2) {
            if (!data.suggestedAddresses[0].id) {
                onError.call(this, data, "error", ajaxErrorMessages.resultMissingId);
                return;
            }

            // Validate our updated address
            verifyAddressCall.call(this, data.suggestedAddresses[0].text);
        }
        // Otherwise, render the suggestions
        else {
            // Render picklist.
            this.renderPicklist(data.suggestedAddresses);
        }
    }

    // Final success handler.
    function onFinalSuccess(data, status) {

        if (!data) {
            onError.call(this, data, "error", ajaxErrorMessages.invalidData);
            return;
        }

        // Display error for non-CA address
        if (data.state.indexOf("CA") === -1 && addressVerifyAllowNonCA == "False") {
            onError.call(this, data, "notCA", this.options.messages.verify.caAddress);
            return;
        }

        reset();

        // Fill in the updated address 
        var $searchBox = $('#objBody_content_0_pagecontent_0_txtAddress').length > 0 ? $("#objBody_content_0_pagecontent_0_txtAddress") : $("#objBody_content_0_leftcolumn_0_txtAddress");
        $searchBox.val(data.selectedAddress);

        renderVerifiedStatus();
    }

    // Success handler
    function renderVerifiedStatus() {
        renderStatus("success-inline", "success", addressValidatedMessage);
    }

    // Renders success status
    function renderStatus(spanClassName, elementClassName, message) {
        var html = [];
        html.push("<div id=\"", elementIds.resultContainerId, "\" class=\"", styles.resultContainer, "\" role=\"alert\" aria-live=\"polite\" aria-atomic=\"true\"><span id=\"addressStatus\" class=\"", spanClassName, "\">", message, "</span></div>");
        setTimeout(function () {
            $("#" + elementIds.resultContainerId).removeAttr("role");
        }, 1000);
        //this.resultContainer.prepend(html);
        var $searchBox = $('#objBody_content_0_pagecontent_0_txtAddress').length > 0 ? $("#objBody_content_0_pagecontent_0_txtAddress") : $("#objBody_content_0_leftcolumn_0_txtAddress");
        $searchBox.after(html.join(""));
    }

    // Clears success status
    function resetStatusElement() {
        $("#addressStatus").remove();
    }

    // Error handler.
    function onError(jqXHR, status, errorThrown) {
        clearLoading();

        // Render error.
        renderErrorElements.call(this, jqXHR, status, errorThrown);
        //this.renderError(jqXHR, status, errorThrown);

        // onError callback.
        if ($.isFunction(this.options.onError)) {
            this.options.onError(jqXHR, status, errorThrown);
        }
    }

    // Get refined associated picklist item.
    function getRefinedAssociatedPicklistItem(refineVal, results) {
        var selected = null;
        var rValue = refineVal.toUpperCase();

        if (rValue) {
            var $addresses = results || [];
            var alphaRefineValMatches = rValue.match(/[a-zA-Z]+/);
            var numRefineValMatches = rValue.match(/[0-9]+/);
            var alphaRefineVal = alphaRefineValMatches ? alphaRefineValMatches[0].toUpperCase().charCodeAt(0) : null;
            var numericRefineVal = numRefineValMatches ? Number(numRefineValMatches[0]) : null;

            $.each($addresses, function (i, v) {
                var decodedAdd = decodeURIComponent(v.text);
                var alphanumeric = decodedAdd.match(/([a-zA-Z0-9]+) \.\.\. ([a-zA-Z0-9]+)/g);
                var hasOddEven = decodedAdd.match(/(\[odd\]|\[even\])/g) || false;
                var isOdd = decodedAdd.match(/\[odd\]/g) || false;
                var isMatch = false;

                if (!hasOddEven || ((!isOdd && (numericRefineVal % 2 === 0)) || (isOdd && (numericRefineVal % 2 !== 0)))) {
                    $(alphanumeric).each(function () {
                        var range = this.split(" ... ");
                        var bottomNumber = range[0].match(/([0-9]+)/g);
                        var topNumber = range[1].match(/([0-9]+)/g);
                        var bottomAlpha = range[0].match(/([a-zA-Z]+)/g);
                        var topAlpha = range[1].match(/([a-zA-Z]+)/g);

                        if (topNumber && bottomNumber) {
                            if (numericRefineVal >= Number(bottomNumber[0]) &&
                                numericRefineVal <= Number(topNumber[0])) {
                                isMatch = true;
                            }
                        } else if (bottomNumber) {
                            if (numericRefineVal === Number(bottomNumber[0])) {
                                isMatch = true;
                            }
                        }

                        if (topAlpha && bottomAlpha /* && isMatch*/ ) {
                            isMatch = false;

                            if (Number(bottomAlpha[0].toUpperCase().charCodeAt(0) !== Number(topAlpha[0].toUpperCase().charCodeAt(0)))) {
                                if (alphaRefineVal >= Number(bottomAlpha[0].toUpperCase().charCodeAt(0)) &&
                                    alphaRefineVal <= Number(topAlpha[0].toUpperCase().charCodeAt(0))) {
                                    isMatch = true;
                                }
                            } else if (Number(topAlpha[0].toUpperCase().charCodeAt(0)) === alphaRefineVal) {
                                isMatch = true;
                            }
                        } else if (topAlpha && alphaRefineVal /*&& isMatch*/ ) {
                            if (Number(topNumber[0]) === numericRefineVal &&
                                alphaRefineVal > Number(topAlpha[0].toUpperCase().charCodeAt(0))) {
                                isMatch = false;
                            }
                        }
                    });
                }

                if (!isMatch) {
                    var strippedAdd = decodedAdd.replace(/([a-zA-Z0-9]+) \.\.\. ([a-zA-Z0-9]+)/g, "");
                    var numericalMatches = strippedAdd.match(/\b([0-9]+)\b/g);

                    $(numericalMatches).each(function () {
                        if (numericRefineVal === Number(this)) {
                            isMatch = true;
                        }
                    });
                }

                if (isMatch) {
                    selected = {
                        action: v.action,
                        query: refineVal,
                        moniker: decodeURIComponent(v.id)
                    };

                    return false;
                }
            });
        }

        return selected;
    }

    /************************
     *** RENDERING METHODS ***
     *************************/
    // Clear result container.
    function clearResult() {
        $("#" + elementIds.resultContainerId).empty();
    }

    // Clear loading indicator.
    function clearLoading() {
        $("#" + elementIds.loadingId).remove();
    }

    // Reset the result.
    function reset() {
        $("#" + elementIds.resultContainerId).remove();
    }

    // Render Result Container.
    function renderResultContainerElements() {
        // clear errors
        $("#" + elementIds.resultContainerId).parent().removeClass("bvalidator_error error-state unverified-address");

        // Don't render resultContainer when it already exists.
        if ($("#" + elementIds.resultContainerId).length != 0) {
            return;
        }

        // Render html.
        var html = '<div id="' + elementIds.resultContainerId + '" class="' + this.options.styles.resultContainer + '" role="alert" aria-live="assertive" aria-atomic="true"></div>';
        this.element.append(html);

        this.resultContainer = $("#" + elementIds.resultContainerId);
    }

    // Render picklist.
    function renderPicklistElements(items, verifyLevel) {
        var $this = this;

        // Remove existing picklist.
        $("#picklist").remove();

        // Render html.
        var html = '<ul id="' + elementIds.picklistId + '" class="' + this.options.styles.picklist + '" >';
        // PWS-745 Refine items for multiple should not be clickable.
        //var isClickable = (verifyLevel && verifyLevel === verificationLevel.Multiple) || false;
        var idPrefix = "picklistItem";

        // Item to use entered value
        html += '<li class="' + $this.options.styles.picklistItem + '" >';
        html += '<input type="button" id="useAddressEntered" class="' + $this.options.styles.picklistItemText + 'btn btn-link" value="' + addressVerifyUseAddress + '" />';
        html += '</li>';

        // Add our pick lsit items
        $.each(items, function (index, value) {
            html += '<li class="' + $this.options.styles.picklistItem + '" >';

            if (value.action === "final" || value.action === "verify" /*|| (value.action === "refine" && isClickable)*/ ) {
                html += '<a id="' + idPrefix + '_' + index + '" href="javascript:void(0);" class="' + $this.options.styles.picklistItemText + '" >';
                html += value.text;
                html += '</a>';
            } else if (value.action === "refine") {
                html += '<p id="' + idPrefix + '_' + index + '" class="' + $this.options.styles.picklistItemText + '" >';
                html += value.text;
                html += '</p>';
            }

            html += '</li>';
        });

        this.resultContainer.append(html);
        setTimeout(function () {
            $(this.resultContainer).removeAttr("role");
        }, 1000);

        html += '</ul>';
    }

    // Bind picklist element events.
    function bindPicklistElementEvents(items) {
        var $this = this;

        if (!items || items.length < 1) {
            return;
        }

        // Bind the use my address item
        $('#useAddressEntered').click($this.reset);

        // Cycle through every picklist item and bind the click event.
        $.each(items, function (index, value) {
            var $picklistItem = $("#picklistItem_" + index);

            if ($picklistItem.attr("href")) {
                // Store data in the element.
                $picklistItem.data({
                    id: value.id,
                    action: value.action,
                    query: value.text
                });

                // Bind click event.
                $picklistItem.click(function () {
                    var $item = $(this);
                    var action = $item.data("action");

                    // Unbind all click event on the picklist.
                    $("#" + elementIds.picklistId + " > li > a").off("click");

                    // Render loading element.
                    renderLoadingElements.call($this, this, true);

                    switch (action) {
                        case "verify":
                            $this.verify($item.text());
                            break;

                        case "refine":
                            var verifyLevel = verificationLevel.PremisesPartial,
                                results = [{
                                    id: $item.data("id"),
                                    text: $item.data("query"),
                                    action: action
                                }];

                            // Clear loading and result.
                            clearLoading();
                            clearResult();

                            $this.renderRefineBox(results, verifyLevel);
                            $this.renderPicklist(results, verifyLevel);
                            break;

                        case "final":
                            var $searchBox = $('#objBody_content_0_pagecontent_0_txtAddress').length > 0 ? $("#objBody_content_0_pagecontent_0_txtAddress") : $("#objBody_content_0_leftcolumn_0_txtAddress");
                            $searchBox.val($item.text());
                            $this.reset();
                            //$this.final($item.data("id"));
                            break;

                    }
                });
            }
        });
    }

    // Render refine container.
    function renderRefineElements(data, verifyLevel) {
        var $this = this;
        var messages = null;
        var verifyMessages = $this.options.messages.verify;

        // Get the display messages.
        switch (verifyLevel) {
            case verificationLevel.InteractionRequired:
                messages = $this.options.messages.interactionRequired;
                break;
            case verificationLevel.PremisesPartial:
                messages = $this.options.messages.premisePartial;
                break;
            case verificationLevel.StreetPartial:
                messages = $this.options.messages.streetPartial;
                break;
            case verificationLevel.NoRefine:
            case verificationLevel.Multiple:
                messages = $this.options.messages.multiple;
                break;
        }

        // Get refine container html.
        var html = getRefineContainerHtml(messages, this.options.styles, verifyLevel, data);

        // Render html.
        this.resultContainer.prepend(html);
        setTimeout(function () {
            $(this.resultContainer).removeAttr("role");
        }, 1000);

        var $refineButton = $("#" + elementIds.refineButtonId);

        // Premises and Street Partial.
        if (verifyLevel === verificationLevel.PremisesPartial || verifyLevel === verificationLevel.StreetPartial) {
            // Focus on the refine box
            setTimeout(function () {
                $("#" + elementIds.refineBoxId).focus();
            }, 500);

            $('.error-state.unverified-address').removeClass('error-state');

            // Bind click event.
            $refineButton.click(function () {
                var $refineError = $("#" + elementIds.refineErrorId)

                // Clear existing error message.
                $refineError.html("");

                // Get refine box value
                var refineBoxValue = $("#" + elementIds.refineBoxId).val();

                // Error on empty unit number
                if (refineBoxValue === null || refineBoxValue === undefined || refineBoxValue === '') {
                    $refineError.html(verifyMessages.addressVerifyEmptyUnitNumber);
                    return;
                }

                // Error on elipses range
                if (refineBoxValue !== null && refineBoxValue !== undefined && (refineBoxValue.indexOf("..") !== -1)) {
                    $refineError.html(messages.error);
                    return;
                }

                // Clear special characters
                refineBoxValue = refineBoxValue.replace(/[^\w\s]/gi, '');


                // Get the associated picklist item based on the refine value.
                var item = getRefinedAssociatedPicklistItem(refineBoxValue, data)

                if (!item) {
                    $refineError.html(messages.error);
                } else {
                    // Unbind click event from refine button.
                    $refineButton.off("click");

                    // Unbind picklist click events.
                    $("#" + elementIds.picklistId + " > li > a").off("click");

                    // Render loading.
                    renderLoadingElements.call($this, this);

                    // Refine.
                    $this.refine(item.query, item.moniker);
                }

                $('.error-state.unverified-address').removeClass('bvalidator_error error-state unverified-address');
            });
        }
        // Interaction required.
        else if (verifyLevel === verificationLevel.InteractionRequired) {

            setTimeout(function () {
                $('input[id$="txtAddress"]').focus();
            }, 500);

            $refineButton.click(function () {
                //var data = {
                //    verifyLevel: verificationLevel.InteractionRequired,
                //    fields: results
                //};

                onFinalSuccess.call($this, data);
            });
            $('#useAddressButton').click($this.reset);
        }
    }

    // Get refine container html.
    function getRefineContainerHtml(messages, styles, verifyLevel, data) {
        // Default: multiple.
        var html = '<div id="' + elementIds.refineContainerId + '" class="' + styles.refineContainer + '">';
        html += '<p id="' + elementIds.refineHeaderId + '" class="' + styles.refineHeader + ' my-0" >' + messages.header.replace(/<[^>]*>?/gm, '') + '</p></div>';
         
        // Add the message text on a multiple result
        if (verifyLevel === verificationLevel.Multiple || verifyLevel === verificationLevel.NoRefine) {
            html += '<span id="' + elementIds.refineTextId + '" class="' + styles.refineText + '" >' + messages.text + '</span>'; 
        }
        // Premises and Street Partials.
        if (verifyLevel === verificationLevel.PremisesPartial || verifyLevel === verificationLevel.StreetPartial) {
            html += '<label for="' + elementIds.refineBoxId + '" class="' + styles.refineLabel + '" >' + messages.text + '</label>';
            html += '<input id="' + elementIds.refineBoxId + '" class="' + styles.refineBox + '" type="text" />';
            html += '<input id="' + elementIds.refineButtonId + '" class="' + styles.refineButton + '" type="button" value="' + messages.button + '" />';
            html += '<br />';
            html += '<span id="' + elementIds.refineErrorId + '" class="refineError"></span>';

            $('.error-state.unverified-address').removeClass('bvalidator_error error-state unverified-address');
        }
        // Interaction required.
        else if (verifyLevel === verificationLevel.InteractionRequired && data.selectedAddress) {
            html += '<p class="' + styles.refineText + '">' + data.selectedAddress + '</p>';
            html += '<p class="d-flex justify-content-between pt-2 mb-0"><input id="' + elementIds.refineButtonId + '" style="display:inline-block;" class="' + styles.refineButton + '" type="button" value="' + messages.button + '" />';
            html += '<input id="useAddressButton" style="display:inline-block;" class="' + styles.refineButton + '" type="button" value="' + addressVerifyUseAddress + '" /></p>';
        }

        html += '</div>';

        return html;
    }

    // Render loading indicator.
    function renderLoadingElements(element, isBefore, message) {
        var msg = message || "";
        var loadingHtml = '<span id="' + elementIds.loadingId + '" class="' + this.options.styles.loadingIndicator + '" role="alert" aria-live="polite" aria-atomic="true" >' + msg + '</span>';

        if (isBefore) {
            $(element).before(loadingHtml);
        } else {
            $(element).after(loadingHtml);
        }
    }

    // Render error elements.
    function renderErrorElements(jqXHR, status, errorThrown, verifyLevel) {
        // Remove any existing 'errorIndicator'.
        $("#" + elementIds.errorIndicatorId).remove();

        // Render html.
        var html = '<span id="' + elementIds.errorIndicatorId + '" class="' + this.options.styles.errorIndicator + '">';

        if (status === "timeout") {
            html += this.options.messages.error.timeout;
        } else if (status === "notFound" || status === "serverNotFound" || status === "emptyAddress" || status === "notCA" || status === "noUnit" || status === "down") {
            html += errorThrown;
        } else {
            html += this.options.messages.error.error;
        }

        html += '</span>';

        // Append the error in the result container.
        this.resultContainer.prepend(html);

        if (verifyLevel !== verificationLevel.PremisesPartial || verifyLevel !== verificationLevel.StreetPartial) {
            $(this.resultContainer).parent().addClass("bvalidator_error error-state unverified-address");
        } else {
            $('.error-state.unverified-address').removeClass('bvalidator_error error-state unverified-address');
        }

        setTimeout(function () {
            $(this.resultContainer).removeAttr("role");
        }, 1000);
    }

})(jQuery, window);